In [8]:
SCOPETYPE = 'OPENADC'
PLATFORM = 'CWLITEXMEGA'
SS_VER = 'SS_VER_2_1'
%run "../../Setup_Scripts/Setup_Generic.ipynb"
INFO: Found ChipWhisperer😍
<class 'chipwhisperer.capture.api.programmers.XMEGAProgrammer'>
scope.adc.offset                         changed from 73700                     to 0                        
scope.adc.samples                        changed from 3000                      to 5000                     
scope.clock.adc_freq                     changed from 86972785                  to 30185691                 
scope.clock.adc_rate                     changed from 86972785.0                to 30185691.0               
In [2]:
%%bash -s "$PLATFORM" "$SS_VER"
cd ../../../hardware/victims/firmware/simpleserial-des
make PLATFORM=$1 CRYPTO_TARGET=AVRCRYPTOLIB  SS_VER=$2 -j
SS_VER set to SS_VER_2_1
SS_VER set to SS_VER_2_1
.
Size after:
+--------------------------------------------------------
avr-gcc (Homebrew AVR GCC 9.4.0) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Welcome to another exciting ChipWhisperer target build!!
+ Built for platform CW-Lite XMEGA with:
   text	   data	    bss	    dec	    hex	filename
   4652	      8	     82	   4742	   1286	simpleserial-des-CWLITEXMEGA.elf
+ CRYPTO_TARGET = AVRCRYPTOLIB
+ CRYPTO_OPTIONS = DES
+--------------------------------------------------------
In [3]:
fw_path = f"/Users/alex/Documents/ChipWhisperer/chipwhisperer/hardware/victims/firmware/simpleserial-des/simpleserial-des-{PLATFORM}.hex"
print(fw_path)
prog = cw.programmers.XMEGAProgrammer
cw.program_target(scope, prog, fw_path)
/Users/alex/Documents/ChipWhisperer/chipwhisperer/hardware/victims/firmware/simpleserial-des/simpleserial-des-CWLITEXMEGA.hex
XMEGA Programming flash...
XMEGA Reading flash...
Verified flash OK, 4659 bytes

We'll probably crash the target a few times while we're trying some glitching. Create a function to reset the target:

In [4]:
def reboot_flush():            
    scope.io.pdic = False
    time.sleep(0.1)
    scope.io.pdic = "high_z"
    time.sleep(0.1)
    #Flush garbage too
    target.flush()
In [5]:
def print_output(returned_data):
    print(returned_data)
    print(f'Cmd: {chr(returned_data[1])}')
    print(f'Len: {returned_data[2]}')
    print(f'Payload (hex): {returned_data[3:-2].hex(" ",1).upper()}')
In [6]:
def capture_traces(cmd, data):
    target.flush()
    scope.arm()
    target.send_cmd(cmd, 0, data)
    capture = scope.capture()
    if capture:
        print("Timeout")
    data = target.read_cmd('r')
    trace = scope.get_last_trace()
    return data, trace
In [9]:
from tqdm.notebook import trange
import numpy as np

# Create project to save traces to
project = cw.create_project("projects/des", overwrite = True)

reboot_flush()

# Display the actual key
target.send_cmd("x", 0, [])
key = target.read_cmd('r')
print_output(key)

ktp = cw.ktp.Basic()
ktp.text_len = 8
ktp.key_len = 8

# We want 3000 samples from two points
scope.adc.samples = 3000            

# Round 1 is performed at offset 73700
# Round 2 is performed at offset 131700
# Your points may be different
offsets = [73700, 131700]

# 200 traces from each run
num_traces = 200

traces = []
for _ in trange(num_traces):
    key, pt = ktp.next()
    long_trace = np.array([])
    
    # Get trace from two offsets and concatenate them
    # so we don't consume/process other data
    for i in range(len(offsets)):
        scope.adc.offset = offsets[i]
        data, trace = capture_traces('p',pt)
        long_trace = np.append(long_trace, trace)

    traces.append(cw.Trace(long_trace, pt, data[3:-2], key))

project.traces.extend(traces)
project.save()
CWbytearray(b'00 72 08 2b 7e 15 16 28 ae d2 a6 e3 00')
Cmd: r
Len: 8
Payload (hex): 2B 7E 15 16 28 AE D2 A6
  0%|          | 0/200 [00:00<?, ?it/s]
In [10]:
# Show entire trace

plot = cw.plot()
for i in range(14):
    plot *= cw.plot(project.traces[i].wave)
plot
Out[10]:
In [11]:
# Due to slight timing variations, we need to ensure that our traces
# are synchronised. Do this here.

import chipwhisperer.analyzer as cwa

resync_traces = cwa.preprocessing.ResyncDTW(project)
resync_traces.ref_trace = 0
resync_traces.radius = 3
resync_analyzer = resync_traces.preprocess()
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 200/200 [00:02<00:00, 83.81it/s]
In [12]:
# And plot it
plt = cw.plot([])
for i in range(14):
    plt *= cw.plot(resync_analyzer.waves[i])
plt
Out[12]:
In [18]:
# CPA for round 1
import warnings
import chipwhisperer.analyzer as cwa

from chipwhisperer.analyzer.attacks.cpa_algorithms.progressive import CPAProgressive
from chipwhisperer.analyzer.attacks.cpa_new import CPA
from chipwhisperer.analyzer.attacks.models.DES import DES, SBox_1_output

warnings.filterwarnings('ignore')

# Round 1 sbox operation starts at trace ~0
start_point= 0

# Results set
results = [[] for _ in range(2)]

leak_model = DES(model=SBox_1_output)
attack = cwa.cpa(resync_analyzer, leak_model)
attack.set_target_subkeys([0, 1, 2, 3, 4, 5, 6, 7])
attack.set_trace_source = attack.project.trace_manager()
attack.set_point_range((start_point,start_point+2200))
cb = cwa.get_jupyter_callback(attack, 8)
results[0] = attack.run(cb)

# Expected round 1 key - 22 10 30 21 32 38 07 3F
Finished traces 175 to 200
  0 1 2 3 4 5 6 7
PGE= 0 0 0 0 0 0 0 0
0 22
0.799
10
0.619
30
0.499
21
0.708
32
0.578
38
0.555
07
0.665
3F
0.601
1 18
0.474
14
0.434
33
0.408
09
0.512
37
0.411
01
0.537
0B
0.356
2C
0.326
2 15
0.464
12
0.366
34
0.403
19
0.489
01
0.388
08
0.516
05
0.347
21
0.323
3 29
0.460
19
0.364
3A
0.373
23
0.482
34
0.385
3C
0.516
26
0.333
22
0.316
4 20
0.426
1D
0.360
3D
0.362
31
0.480
22
0.364
07
0.494
01
0.324
0E
0.311
In [15]:
from textwrap import wrap
from chipwhisperer.analyzer.attacks.models.DES import DESLeakageHelper

# We now want to take the round 1 key from above,
# calculate hypothetical round 2 input
# and save it for running the round 2 attack

round_2 = cw.create_project("projects/des_round_2", overwrite = True)

des = DESLeakageHelper()

key = 0
for i, subkey in enumerate(results[0].best_guesses()):
    key |= int(subkey["guess"]) << ((7-i) * 6)

key = f'{key:048b}'

# We'll take the synced traces project for input

for trace in resync_analyzer.traces:
    pt = ''.join(f'{i:02X}' for i in trace[1])
    
    input = des.hex_to_bin(pt)
    
    # Get R1 output/R2 input
    output, _ = des.get_round_1_output(input, key)
    ct = []
    binlist = wrap(output, 8)
    for bin in binlist:
        ct.append(int(bin,2))
    
    new_trace = cw.Trace(trace[0],ct,[0]*8,[0]*8)
    round_2.traces.append(new_trace)

# Save the project
round_2.save()
In [16]:
# CPA for round 2
import warnings
warnings.filterwarnings('ignore')

import chipwhisperer.analyzer as cwa
from chipwhisperer.analyzer.attacks.models.DES import SBox_2_output

start_point=3100

leak_model = DES(model=SBox_2_output)
attack = cwa.cpa(round_2, leak_model)
attack.set_target_subkeys([0, 1, 2, 3, 4, 5, 6, 7])
attack.set_trace_source = attack.project.trace_manager()
attack.set_point_range((start_point,start_point+2500))
cb = cwa.get_jupyter_callback(attack, 8)
results[1] = attack.run(cb)

# Expected round 2 key - 16 1A 01 0A 3D 1E 1F 2A
Finished traces 175 to 200
  0 1 2 3 4 5 6 7
PGE= 18 25 44 14 47 14 38 27
0 16
0.830
1A
0.616
01
0.601
0A
0.604
3D
0.534
1E
0.612
1F
0.640
2A
0.639
1 1D
0.509
18
0.385
17
0.414
25
0.512
0B
0.377
01
0.402
04
0.390
2B
0.410
2 34
0.468
13
0.377
2E
0.383
1A
0.459
1B
0.345
23
0.374
18
0.356
05
0.346
3 14
0.467
1E
0.362
1C
0.378
22
0.441
14
0.324
0E
0.356
10
0.352
28
0.334
4 09
0.459
35
0.358
05
0.351
0E
0.428
38
0.320
3A
0.354
26
0.345
37
0.317
In [17]:
# Expected round keys:
#
# round 1 key - 22 10 30 21 32 38 07 3F
# round 2 key - 16 1A 01 0A 3D 1E 1F 2A

# Calculate the full 64-bit key (parity bits are not calculated)

des = DESLeakageHelper()

key    = [[] for _ in range(2)]
binkey = ['' for _ in range(2)]

for i,k in enumerate(key):
    print(f'Best guess key (R{i+1}): ', end='')
    for subkey in results[i].best_guesses():
        key[i].append(subkey["guess"])
        print(f'{subkey["guess"]:0>2X}', end=' ')
    print()
    
    round_key = des.get_round_key(key[i],i+1,0, False)
    for bit in round_key:
        binkey[i] += str(bit).replace('?','0')


xor_round_keys = f'{int(binkey[0],2) | int(binkey[1],2):02X}'

wrap(xor_round_keys, 2)


# Expected key without parity bits set
# 2B 7E 14 16 28 AE D2 A6
Best guess key (R1): 22 10 30 21 32 38 07 3F 
Best guess key (R2): 16 1A 01 0A 3D 1E 1F 2A 
Out[17]:
['2A', '7E', '14', '16', '28', 'AE', 'D2', 'A6']